
""" Manages Presentation objects - no GUI work
"""

"""
    Copyright 2008 Graham King <graham@gkgk.org>
    
    This file is part of Keithnote.

    Keithnote is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Keithnote is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Keithnote.  If not, see <http://www.gnu.org/licenses/>.
"""

import glob
import os
import os.path
import urllib
import shutil

import wx
import flickrapi

DEBUG = True

API_KEY = "2ed5d0334ebdfbb9e512d6ac699cd55b"
DEV_USER_DIR = "/home/graham/.Keithnote"
IGNORE_WORDS = ['the', 'and', ]

class Presentation:
    "A presentation"

    def __init__(
                 self, 
                 title, 
                 author, 
                 affiliation, 
                 tags, 
                 num_slides,
                 sort,
                 user_data_dir, 
                 ident = None,
                 progress_dialog = None):
        
        self.title = title
        self.author = author
        self.affiliation = affiliation
        self.tags = tags
        self.num_slides = num_slides
        self.sort = sort
        self.credits = []
        
        if ident:
            self.ident = ident
            self._load_credits()
        else:
            self.ident = _next_id(user_data_dir)

        self.dir = user_data_dir + os.path.sep + str(self.ident)
        
        self.soup1 = _tagsouperize(self.author, self.affiliation)
        self.soup2 = _tagsouperize(self.title, self.tags)    
        
        self.progress_dialog = progress_dialog
        
    def __str__(self):
        return self.title +" by "+ self.author
    
    def progress(self, value, msg):
        'Update a progress bar to this amount and this message'
        
        if self.progress_dialog:
            wx.CallAfter(self.progress_dialog.Update, value, str(value) +". "+ msg)

    def delete(self):
        'Delete this presentation from the disc'
        for entry in glob.glob(self.dir +os.path.sep+ '*'):
            os.remove(entry)
        os.rmdir(self.dir)

    def save(self):
        'Write this presentation to disc'
        _ensure_path_exists(self.dir)
        self._save_data()

    def _save_data(self):
        'Write the data fields of this presentation to file'
        
        data_file = open(self.dir +os.path.sep+ 'data.txt', 'wt')
        data_file.write(self.title +'\n')
        
        if self.author:
            data_file.write(self.author)
        data_file.write('\n')
        
        if self.affiliation:
            data_file.write(self.affiliation)
        data_file.write('\n')
        
        if self.tags:
            data_file.write(self.tags)
        data_file.write('\n')
        
        if self.sort:
            data_file.write(self.sort)
        data_file.write('\n')
        
        data_file.close()
   
    def fetch_slides(self):
        'Download images suitable for this presentation from Flickr'
        
        _ensure_path_exists(self.dir)

        flickr = flickrapi.FlickrAPI(API_KEY)

        self.progress(0, "Fetching image options")
        speaker_slide_result = fetch_slide_options(flickr, self.soup1, self.sort)
        speaker_slides = speaker_slide_result.photos[0].photo
        
        topic_slide_result = fetch_slide_options(flickr, self.soup2, self.sort)
        topic_slides = topic_slide_result.photos[0].photo
        
        num_speaker_slides = int(self.num_slides / 3)
        
        for slide_num in range(1, self.num_slides+1):
            self.progress(slide_num, "Fetching slide")
            
            if slide_num <= num_speaker_slides:
                photo = speaker_slides[slide_num - 1]
            else:
                photo= topic_slides[slide_num - num_speaker_slides - 1]

            if DEBUG:
                print "fetch_slides photo.attrib:", photo.attrib
            
            self.progress(slide_num, "Downloading picture")
            self._save_photo(flickr, photo, slide_num)
        
        self._save_credits()
            
    def _credit_filename(self):
        'The full path and name of the file where the credits are stored'
        return _user_dir() +os.path.sep+ str(self.ident) +os.path.sep+ 'credits.txt'
            
    def _load_credits(self):
        'Load the credits from disc'
        credit_file = open(self._credit_filename(), 'rt')
        credit_data = credit_file.readlines()
        credit_file.close()
        
        for line in range(0, len(credit_data), 2):
            title = unicode( credit_data[line], 'utf-8' )
            author = unicode( credit_data[line+1], 'utf-8' )
            self.credits.append( (title.strip(), author.strip()) )
    
    def _save_credits(self):
        'Save the photo credits to a file'
        
        credit_file = open(self._credit_filename(), 'wt')
        
        for (title, author) in self.credits:
            credit_file.write(title +'\n')
            credit_file.write(author +'\n')
        
        credit_file.close()
            
    def _save_photo(self, flickr, photo, slide_num):
        'Download a picture and save it slide slide_num of this presentation'
        
        url = _photo_best_size_url(flickr, photo)
        if DEBUG:
            print "_save_photo url:", url
        
        (temp_filename, _) = urllib.urlretrieve(url)
        ext = temp_filename[-3:]
        
        if slide_num < 10:
            slide_name = 'slide0'+ str(slide_num) +'.'+ ext
        else:
            slide_name = 'slide'+ str(slide_num) +'.'+ ext
        
        target_filename = self.dir +os.path.sep+ slide_name
        if DEBUG:
            print "_save_photo target_filename:", target_filename
        shutil.move(temp_filename, target_filename)

        self.progress(slide_num, "Fetching user information")
        user_id = photo['owner']
        
        author = fetch_username(flickr, user_id)
        title = photo['title'].encode('utf-8')
        self.credits.append( (title, author) )

    def slides(self):
        'Full directory path of all the images for this presentation'

        slide_list = list( glob.glob(self.dir +os.path.sep+ 'slide*') )
        slide_list.sort()
        return slide_list


def create(title, author, affiliation, tags, num_slides, sort, progress_dialog):
    "Create a new presentation"
    
    user_dir = _user_dir()
    if DEBUG:
        print "create user_idr:", user_dir
    
    presentation = Presentation(
                                title, 
                                author, 
                                affiliation, 
                                tags, 
                                int(num_slides),
                                sort,
                                user_dir, 
                                progress_dialog=progress_dialog)
    presentation.save()

    presentation.fetch_slides()
    
def get_presentations():
    'List of presentations'
    
    presentations = []
    
    user_dir = _user_dir()
    for pres_dir in glob.glob(user_dir +os.path.sep+ '*'):
        pres = _load_presentation_from_dir(pres_dir)
        presentations.append(pres)
        
    return presentations

def load_presentation(ident):
    "Load the presentation for id 'ident'"

    return _load_presentation_from_dir(_user_dir() +os.path.sep+ str(ident))

def _load_presentation_from_dir(pres_dir):
    'Load a presentation from disk'
    
    data = open(pres_dir +os.path.sep+ 'data.txt')
    lines = data.readlines()
    data.close()
    
    (_, ident) = os.path.split(pres_dir)
    return Presentation(
                        lines[0].strip(),
                        lines[1].strip(),
                        lines[2].strip(),
                        lines[3].strip(),
                        -1,    # num_slides - we don't know it at this stage
                        lines[4].strip(),
                        _user_dir(),
                        ident
                        )

def _user_dir():
    'Directory where this app should store data'
    app = wx.GetApp()
    if app:
        user_dir = wx.StandardPaths.Get().GetUserDataDir()
    else:
        print "NOT wx - DEVELOPMENT MODE"
        user_dir = DEV_USER_DIR

    return user_dir

def _photo_best_size_url(flickr, photo):
    'The URL of the best size for this photo'
    
    sizes = flickr.photos_getSizes(photo_id = photo['id'])

    size_map = {}
    for size in sizes.sizes[0].size:
        size_map[ size['label'] ] = size
    
    if 'Large' in size_map.keys():
        selected_size = 'Large'
    elif 'Original' in size_map.keys():
        selected_size = 'Original'
    else:
        selected_size = 'Medium'

    if DEBUG:
        print "photo_best_size_url selected_size: ", selected_size
    
    return size_map[selected_size]['source']

def fetch_slide_options(flickr, keywords, sort):
    'A list of picture options (as XMLNode objects) for the given keywords (as an array)'
    
    if DEBUG:
        print "fetch_slide_options keywords:", keywords , " sort:", sort
    
    search_string = ' OR '.join(keywords)
    photos = flickr.photos_search(
                                  text=search_string,
                                  per_page=100,
                                  sort = sort,
                                  license="1,2,3,4,5,6,7"
                                  )
    return photos

def fetch_username(flickr, user_id):
    'The full name of the Flickr user with the given id'
    
    res = flickr.people_getInfo(user_id = user_id)
    username = res.person[0].username[0].text
    
    if DEBUG:
        print "fetch_username user_id: ", user_id
        print "fetch_username username: ", username
    
    return username.encode('utf-8')

def _ensure_path_exists(path):
    """
     Creates all the components of a directory path needed to make it exist.
     If you pass '/usr/local/bill/jam/donuts/' and only 'usr/local/bill' exist,
     then jam and donuts will be created.
     Returns true if it created the path, false if it was already there.
    """
    if not os.path.exists(path):
        os.makedirs(path)
        return True
    return False

def _next_id(user_data_dir):
    'The next available identifier for this presentation'
    
    ident = 1
    while os.path.exists(user_data_dir +os.path.sep+ str(ident) ):
        ident += 1
    
    return str(ident)

def _tagsouperize(str1, str2):
    """Takes two strings, splits them into words, filters out the short ones, and returns
    the resulting list of strings in lower case"""
    
    all_tags = []
    if str1:
        all_tags.extend( str1.split(" ") )
        
    if str2:
        all_tags.extend( str2.split(" ") )
    
    return [x.lower() for x in all_tags if len(x) > 2 and not x in IGNORE_WORDS]
